/*
 * Copyright 2012 Marsrutai.lt
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package lt.marsrutai.mobile.android.activity;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import lt.marsrutai.mobile.android.R;
import lt.marsrutai.mobile.android.activity.intent.PointSelectionIntentHolder;
import lt.marsrutai.mobile.android.util.ParcelUtil;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.view.animation.BounceInterpolator;
import android.view.animation.Interpolator;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener;
import com.google.android.gms.maps.GoogleMap.OnMapClickListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener;
import com.google.android.gms.maps.Projection;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

/**
 * Sub-activity for selecting a point on a map 
 * 
 * @author Justas
 */
public class PointSelectionMapActivity extends BaseMapActivity implements OnMapClickListener, OnInfoWindowClickListener, OnMarkerDragListener {

    /**
     * <p>
     * Contains points that have been selected/provided and are displayed on the
     * map.
     * </p>
     * <p>
     * Items that do not have location info ( {@link MarkerInfo#getLatLng()} is
     * <code>null</code>) will have this info filled, when the user clicks on
     * the map (by trying to select a point).
     * </p>
     * <p>
     * Such "empty" items, in combination with {@link #maxPoints}, control
     * the total count of points that the user can select on the map.
     * </p>
     */
    private List<MarkerInfo> markerInfoList;

    /**
     * <p>Indicates how many points a user can select on the map. Works in combination with {@link #markerInfoList}</p>
     * <p>
     *  <ul>
     *      <li>0 means that points that the user can select is controlled by {@link #markerInfoList}</li>
     *      <li>Any value that less than the size of {@link #markerInfoList} will be ignored.</li>
     *  </ul>
     * </p>
     */
    private int maxPoints;

    /**
     * Maps items from markerInfoList to their respective markers
     */
    private Map<String, MarkerInfo> markerInfoMap = new HashMap<String, MarkerInfo>();
    
    @Override
    protected void setUpMap(GoogleMap map, Bundle savedInstanceState) {
        super.setUpMap(map, savedInstanceState);

        map.setOnMapClickListener(this);
        map.setOnInfoWindowClickListener(this);
        map.setOnMarkerDragListener(this);

        if (savedInstanceState == null) {
            List<LatLng> points = getPointsToAnimateTo();
            
            if (points.size() > 0) {
                zoomToItems(points);
            }
            else {
                animateToCurrentLocation();
            }
        }
        
        if (markerInfoList != null) {
            for (MarkerInfo markerInfo : markerInfoList) {
                // Draw the marker only if it has location data
                if (markerInfo.getLatLng() != null) {
                    addMarker(markerInfo);
                }
            }
        }
    }

    protected Marker addMarker(MarkerInfo markerInfo) {
        MarkerOptions markerOptions = new MarkerOptions()
                .position(markerInfo.getLatLng())
                .title(markerInfo.getTitle())
                .snippet(markerInfo.getDescription())
                .draggable(true);
        
        if (markerInfo.getIcon() != 0) {
            markerOptions.icon(BitmapDescriptorFactory.fromResource(markerInfo.getIcon()));
        }
        
        Marker marker = getMap().addMarker(markerOptions);
        markerInfoMap.put(marker.getId(), markerInfo);
        
        return marker;
    }
    
    private List<LatLng> getPointsToAnimateTo() {
        List<LatLng> points = new ArrayList<LatLng>();
        
        for (MarkerInfo markerInfo : markerInfoList) {
            LatLng latLng = markerInfo.getLatLng();
            if (markerInfo.getAnimateToInd() && latLng != null) {
                points.add(latLng);
            }
        }
        
        return points;
    }
    
    @Override
    protected void onCreateInternal(Bundle savedInstanceState) {
        super.onCreateInternal(savedInstanceState);
        
        Bundle bundle = savedInstanceState == null ? getIntent().getExtras() : savedInstanceState;
        PointSelectionIntentHolder intentHolder = new PointSelectionIntentHolder(bundle);
        markerInfoList = intentHolder.getMarkers();
        maxPoints = intentHolder.getMaxPoints();
    }
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        PointSelectionIntentHolder intentHolder = new PointSelectionIntentHolder(new Bundle());
        intentHolder.setMarkers(markerInfoList);
        intentHolder.setMaxPoints(maxPoints);
        
        outState.putAll(intentHolder.getIntent().getExtras());
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onMapClick(final LatLng latLng) {
        Marker marker = null;
        
        MarkerInfo emptyMarkerInfo = getEmptyMarkerInfo();
        if (emptyMarkerInfo != null) {
            // Fill empty markers
            emptyMarkerInfo.setLatLng(latLng);
            
            marker = addMarker(emptyMarkerInfo);
        }
        else if (markerInfoList.size() < maxPoints) {
            MarkerInfo markerInfo = new MarkerInfo(null, getString(R.string.tap_to_select_point), null, 0, latLng, false);
            markerInfoList.add(markerInfo);
            marker = addMarker(markerInfo);
        }
        
        if (marker != null) {
            animateMarkerDrop(marker, latLng);
            marker.showInfoWindow();
        }
    }

    /**
     * Returns the first empty marker (if there is one)
     * 
     * @return
     */
    private MarkerInfo getEmptyMarkerInfo() {
        for (MarkerInfo markerInfo : markerInfoList) {
            if (markerInfo.getLatLng() == null) {
                return markerInfo;
            }
        }
        return null;
    }

    @Override
    public void onMarkerDrag(Marker marker) {
    }

    @Override
    public void onMarkerDragEnd(Marker marker) {
        animateMarkerDrop(marker, marker.getPosition());

        markerInfoMap.get(marker.getId()).setLatLng(marker.getPosition());
    }

    @Override
    public void onMarkerDragStart(Marker marker) {
    }

    @Override
    public void onInfoWindowClick(Marker marker) {
        PointSelectionIntentHolder intentHolder = new PointSelectionIntentHolder(new Bundle());
        intentHolder.setMarkers(markerInfoList);
        
        setResult(RESULT_OK, intentHolder.getIntent());
        finish();
    }

    protected void animateMarkerDrop(final Marker marker, final LatLng latLng) {
        final Handler handler = new Handler();
        final long start = SystemClock.uptimeMillis();
        Projection proj = getMap().getProjection();
        Point startPoint = proj.toScreenLocation(latLng);
        startPoint.offset(0, -100);
        final LatLng startLatLng = proj.fromScreenLocation(startPoint);
        final long duration = 500;
   
        final Interpolator interpolator = new BounceInterpolator();
   
        handler.post(new Runnable() {
            @Override
            public void run() {
                long elapsed = SystemClock.uptimeMillis() - start;
                float t = interpolator.getInterpolation((float) elapsed / duration);
                double lng = t * latLng.longitude + (1 - t) * startLatLng.longitude;
                double lat = t * latLng.latitude + (1 - t) * startLatLng.latitude;
                marker.setPosition(new LatLng(lat, lng));
   
                if (t < 1.0) {
                    // Post again 16ms later.
                    handler.postDelayed(this, 16);
                }
            }
        });
    }

    /**
     * 
     * @author Justas
     */
    public static class MarkerInfo implements Parcelable {
        
        public static final Parcelable.Creator<MarkerInfo> CREATOR = new Parcelable.Creator<MarkerInfo>() {

            @Override
            public MarkerInfo createFromParcel(Parcel in) {
                return new MarkerInfo(in);
            }

            @Override
            public MarkerInfo[] newArray(int size) {
                return new MarkerInfo[size];
            }
        };
        
        private String id;
        private String title;
        private String description;
        private int icon;
        private LatLng latLng;
        private boolean animateToInd;

        public MarkerInfo() {
        }

        public MarkerInfo(String id, String title, String description, int icon, LatLng latLng, boolean animateToInd) {
            this.id = id;
            this.title = title;
            this.description = description;
            this.icon = icon;
            this.latLng = latLng;
            this.animateToInd = animateToInd;
        }

        public MarkerInfo(Parcel in) {
            id = in.readString();
            title = in.readString();
            description = in.readString();
            icon = in.readInt();
            latLng = in.readParcelable(LatLng.class.getClassLoader());
            animateToInd = ParcelUtil.readBoolean(in);
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(id);
            dest.writeString(title);
            dest.writeString(description);
            dest.writeInt(icon);
            dest.writeParcelable(latLng, flags);
            ParcelUtil.writeBoolean(dest, animateToInd);
        }

        @Override
        public int describeContents() {
            return 0;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        public int getIcon() {
            return icon;
        }

        public void setIcon(int icon) {
            this.icon = icon;
        }

        public LatLng getLatLng() {
            return latLng;
        }

        public void setLatLng(LatLng latLng) {
            this.latLng = latLng;
        }

        public boolean getAnimateToInd() {
            return animateToInd;
        }

        public void setAnimateToInd(boolean animateToInd) {
            this.animateToInd = animateToInd;
        }

    }
}
